package game;

import game.Audio.AudioResources;
import game.Rendering.GameCanvas;
import game.Utils.AnimatedDouble;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import org.json.simple.parser.ParseException;

import java.util.HashMap;
import javax.sound.sampled.*;
import java.io.IOException;
import java.io.InputStream;


class StartMenu {
    private static Pane root;
    private static GridPane subMenuPane;
    private static GridPane bottomButts;
    private static HashMap<String, TextField> textFields;
    private static HashMap<String, CheckBox> checkBoxes;
    private static HashMap<String, ComboBox> comboBoxes;
    private static ListView<String> mapList;

    boolean loading = false;
    protected AnimatedDouble loadingAnim = new AnimatedDouble(0.0, 1.0, 0.3, false);

    private Clip clip;

    /***
     * Adds new Label and TextField to the given GridPane, adds TextField to HashMap with key=labelText
     *
     * @param grid              GridPane where both Label and TextField will be added.
     * @param labelText         Text displayed in the Label, also works as key for the TextField in the HashMap.
     * @param defaultText       Default text to show in the TextField (empty if null).
     * @param isNum             If true the TextField is limited to digits only.
     * @param row               Row where both Label and TextField will be added in the GridPane.
     */
    private void addLabelAndText(GridPane grid, String labelText, String defaultText, boolean isNum, int row, Integer maxValue, Integer minValue) {
        TextField tf = new TextField();
        if (defaultText != null) {
            tf.setText(defaultText);
        }
        if (isNum) {
            tf.focusedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue, Boolean newPropertyValue) {
                    if (!newPropertyValue) {
                        String aux = tf.getText();
                        //Make sure the value is equal or lesser than the given max value
                        if (maxValue != null) {
                            try {
                                if (!aux.equals("")) {
                                    try {
                                        if (Integer.parseInt(aux) > maxValue) aux = String.valueOf(maxValue);
                                        else if (Integer.parseInt(aux) < minValue) aux = String.valueOf(minValue);
                                    } catch (Exception e) {
                                        aux = String.valueOf(minValue);
                                    }

                                } else aux = String.valueOf(minValue);

                            } catch (Exception e) {
                                    e.printStackTrace();
                            }
                        }
                        String finalAux = aux;
                        tf.setText(finalAux);
                    }
                }
            });
            tf.textProperty().addListener((observable, oldValue, newValue) -> {
                String aux = newValue;
                //Remove letters
                if (!newValue.matches("\\d*")) {
                    aux = aux.replaceAll("[^\\d]", "");
                }
                String finalAux = aux;
                tf.setText(finalAux);

            });
        }
        grid.add(new Label(labelText), 0, row);
        grid.add(tf, 1, row);
        textFields.put(labelText, tf);
    }

    /***
     * Adds new Label and CheckBox to the given GridPane, adds CheckBox to HashMap with key=labelText
     *
     * @param grid              GridPane where both Label and CheckBox will be added.
     * @param labelText         Text displayed in the Label, also works as key for the CheckBox in the HashMap.
     * @param row               Row where both Label and CheckBox will be added in the GridPane.
     */
    private void addLabelAndCheck(GridPane grid, String labelText, int row, boolean checked) {
        GridPane aux = new GridPane();
        CheckBox cb = new CheckBox();
        cb.setSelected(checked);
        aux.add(cb, 0, 0);
        aux.add(new Label(labelText), 1, 0);
        grid.add(aux, 1, row);
        checkBoxes.put(labelText, cb);
    }


    /***
     * Adds new Label and ComboBox to the given GridPane, adds ComboBox to HashMap with key=labelText
     *
     * @param grid              GridPane where both Label and ComboBox will be added.
     * @param labelText         Text displayed in the Label, also works as key for the ComboBox in the HashMap.
     * @param options           List of options available in the ComboBox.
     * @param row               Row where both Label and TextField will be added in the GridPane.
     */
    private void addLabelAndCombo(GridPane grid, String labelText, ObservableList<String> options, int row) {
        ComboBox cb = new ComboBox(options);
        cb.getSelectionModel().selectFirst();
        grid.add(new Label(labelText), 0, row);
        grid.add(cb, 1, row);
        comboBoxes.put(labelText, cb);
    }

    /***
     * Returns menu's Map tab
     */
    private Tab gameTab() {
        Tab t = new Tab("Game");

        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(0);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        ObservableList<String> gameModeOptions = FXCollections.observableArrayList("Team Deathmatch", "Standard");
        addLabelAndCombo(grid, "Game Mode:", gameModeOptions, 0);

        ComboBox modes = comboBoxes.get("Game Mode:");
        modes.getSelectionModel().selectedItemProperty().addListener((options, oldValue, newValue) -> {
                    if (modes.getSelectionModel().getSelectedIndex() == 0) {
                        ObservableList<String> items = FXCollections.observableArrayList("v_labyrinth", "v_SmallCity1.2", "cs_office",
                                "de_aztec_classic", "dm_grenadelauncher", "de_inferno", "dm_laser", "as_snow",
                                "con_sandbox", "con_tinysandbox", "cs_assault", "ctf_cs2dfortress", "ctf_temple", "de_embrador", "dm_guerilla",
                                "dm_rpgwar", "dm_snowball", "dom_cs2dfortress", "dom_oasis", "fy_ggsas", "zm_street");
                        mapList.setItems(items);
                        mapList.getSelectionModel().selectFirst();
                    } else {
                        ObservableList<String> items = FXCollections.observableArrayList("v_labyrinth", "v_SmallCity1.2", "de_aztec_classic", "de_embrador");
                        mapList.setItems(items);
                        mapList.getSelectionModel().selectFirst();
                    }
                }
        );
        StackPane aux = new StackPane();
        aux.setAlignment(Pos.TOP_LEFT);
        aux.setPadding(new Insets(20, 0, 20, 0));
        ListView<String> list = new ListView<String>();
        //Critical fail: "as_mars","cs_italy", "ctf_fast", "de_aztec", "de_cbble", "de_cs2d", "de_cs2d_classic",
        //      "de_desert", "de_dust", "de_dust2", "de_eldorado","de_takedown", "de_vantage", "dm_grenades", "fy_shipment", "temp"
        //ObservableList<String> items = FXCollections.observableArrayList("as_mars","cs_italy", "ctf_fast", "de_aztec",
        // "de_cbble", "de_cs2d", "de_cs2d_classic", "de_desert", "de_dust", "de_dust2", "de_eldorado", "de_takedown",
        // "de_vantage", "dm_grenades", "fy_shipment", "temp");

        //Fail: "ctf_warehouse_v17","de_dust_classic","de_dust2_classic","fy_industrial_v3", "dom_boondocks"
        //ObservableList<String> items = FXCollections.observableArrayList("ctf_warehouse_v17","de_dust_classic","de_dust2_classic","fy_industrial_v3", "dom_boondocks");

        //Short test passed: "v_labyrinth", "v_SmallCity1.2", "cs_office", "de_aztec_classic", "dm_grenadelauncher", "de_inferno", "dm_laser", "as_snow",
        // "con_sandbox","con_tinysandbox", "cs_assault", "ctf_cs2dfortress", "ctf_temple", "de_embrador", "dm_guerilla",
        // "dm_rpgwar", "dm_snowball", "dom_cs2dfortress", "dom_oasis", "fy_ggsas", "zm_street"
        ObservableList<String> items = FXCollections.observableArrayList("v_labyrinth", "v_SmallCity1.2", "cs_office",
                "de_aztec_classic", "dm_grenadelauncher", "de_inferno", "dm_laser", "as_snow",
                "con_sandbox", "con_tinysandbox", "cs_assault", "ctf_cs2dfortress", "ctf_temple", "de_embrador", "dm_guerilla",
                "dm_rpgwar", "dm_snowball", "dom_cs2dfortress", "dom_oasis", "fy_ggsas", "zm_street");

        list.setItems(items);
        list.getSelectionModel().selectFirst();
        mapList = list;
        aux.getChildren().add(list);
        grid.add(aux, 1, 1);
        ColumnConstraints col1Constraints = new ColumnConstraints();
        col1Constraints.setPercentWidth(25);
        ColumnConstraints col2Constraints = new ColumnConstraints();
        col2Constraints.setPercentWidth(75);
        grid.getColumnConstraints().addAll(col1Constraints, col2Constraints);
        t.setContent(grid);
        return t;
    }

    /***
     * Returns menu's Settings tab
     */
    private Tab settingsTab() {
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(0);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        Tab t = new Tab("Settings");

        int row = 0;

        ObservableList<String> spectatorOptions = FXCollections.observableArrayList("Everything", "Nothing (War Mode)", "Own Team only");
        //addLabelAndCombo(grid, "Allow to Spectate:", spectatorOptions, row);

        addLabelAndText(grid, "Win limit (Rounds):", "15", true, row++, 30, 1);
        addLabelAndText(grid, "Team Round Limit (Rounds):", "15", true, row++, 30, 1);
        addLabelAndText(grid, "Time per Round (Sec.):", "120", true, row++, 240, 30);
        addLabelAndText(grid, "Freeze Time (Sec.):", "5", true, row++, 60, 1);
        addLabelAndText(grid, "Buy Time (Sec.):", "10", true, row++, 60, 0);
        addLabelAndText(grid, "Start Money:", "800", true, row++, 16000, 0);

        addLabelAndCheck(grid, "Fog of War", row++, true);
        addLabelAndCheck(grid, "Friendly Fire", row++, false);
        addLabelAndCheck(grid, "Counter Terrorist Team", row++, false);
        //addLabelAndCheck(grid, "Auto Teambalance", row);

        t.setContent(grid);

        return t;
    }

    /***
     * Returns menu's Bots tab
     */
    private Tab botsTab() {
        GridPane grid = new GridPane();
        grid.setPadding(new Insets(10, 10, 10, 10));
        grid.setVgap(10);
        grid.setHgap(5);
        grid.setAlignment(Pos.TOP_LEFT);
        Tab t = new Tab("Bots");

        int row = 0;
        addLabelAndText(grid, "Bot Name Prefix:", "[b]", false, row++, null, null);
        addLabelAndText(grid, "Bots:", "3", true, row++, 10, 1);

        //addLabelAndCheck(grid, "Auto Fill", row++);

        //ObservableList<String> skillOptions = FXCollections.observableArrayList("Very Low", "Low", "Normal", "Advanced", "Professional");
        ObservableList<String> skillOptions = FXCollections.observableArrayList("Advanced", "Normal", "Low");
        addLabelAndCombo(grid, "Skills:", skillOptions, row++);

        ObservableList<String> weaponOptions = FXCollections.observableArrayList("All Weapons", "Melee only", "Pistols only", "Shotguns only", "SMGs only", "Rifles only", "Sniper Rifles only", "MGs only");
        //addLabelAndCombo(grid, "Weapons:", weaponOptions, row);

        t.setContent(grid);

        return t;
    }

    public void draw(GraphicsContext gc){
        double screenWidth = (double) Helper.screenWidth();
        double screenHeight = (double) Helper.screenHeight();
        if(loading) {
            gc.setFill(new Color(0, 0, 0, loadingAnim.getValue()));
            gc.fillRect(0, 0, screenWidth, screenHeight);
            gc.setTextAlign(TextAlignment.CENTER);
            gc.setFill(new Color(1, 1, 1, loadingAnim.getValue()));
            String mapName = mapList.getSelectionModel().getSelectedItem();
            gc.fillText("loading " + mapName + "...", screenWidth/2, screenHeight/2);
        }

        if(loading && loadingAnim.getValue() >= 1.0){
            loading = false;
            new StartGame();
        }
    }

    public boolean isLoading(){
        return loading;
    }
    public StartMenu(Clip clip) throws IOException, ParseException {
        this.clip = clip;
        root = GameCanvas.getPane();
        textFields = new HashMap<String, TextField>();
        checkBoxes = new HashMap<String, CheckBox>();
        comboBoxes = new HashMap<String, ComboBox>();
        int width = Helper.screenWidth(), height = Helper.screenHeight();
        Color lightG = new Color(55 / 255.0, 56 / 255.0, 60 / 255.0, 255 / 255.0);
        Background lightB = new Background(new BackgroundFill(lightG, new CornerRadii(10), null));
        Color darkG = new Color(50 / 255.0, 50 / 255.0, 50 / 255.0, 255 / 255.0);
        Background darkB = new Background(new BackgroundFill(darkG, null, null));
        subMenuPane = new GridPane();
        subMenuPane.setBackground(lightB);
        double windowWidth = 340;
        double windotHeight = 360;
        subMenuPane.setPrefSize(windowWidth, windotHeight);
        subMenuPane.setMaxSize(windowWidth, windotHeight);
        subMenuPane.setMinSize(windowWidth, windotHeight);
        subMenuPane.setLayoutX(width / 2.0 - windowWidth / 2.0);
        subMenuPane.setLayoutY(height / 2.0 - windotHeight / 2.0);
        root.getStylesheets().add(getClass().getClassLoader().getResource("styles/startMenu.css").toExternalForm());

        //Tabs
        TabPane tabs = new TabPane();
        tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
        tabs.getTabs().add(gameTab());
        tabs.getTabs().add(settingsTab());
        tabs.getTabs().add(botsTab());
        subMenuPane.add(tabs, 0, 0);

        Button start = new Button("Start");
        start.setOnMouseClicked(e -> {
            try {
                playButtonSound();
            } catch (Exception ex) {
            }
            clip.stop();
            if (ConfigurationMenu.isVisible()) ConfigurationMenu.destroyMenu();
            loading = true;
            hideMenu();
            loadingAnim.reset();
        });
        Button cancel = new Button("Cancel");
        cancel.setOnMouseClicked(e -> {
            try {
                playExitButtonSound();
            } catch (Exception ex) {
            }
            hideMenu();
        });

        bottomButts = new GridPane();
        bottomButts.setAlignment(Pos.BOTTOM_RIGHT);
        bottomButts.setPadding(new Insets(0, 20, 20, 50));
        bottomButts.setHgap(20);
        bottomButts.add(start, 1, 0);
        bottomButts.add(cancel, 2, 0);
        subMenuPane.add(bottomButts, 0, 1);

        root.getChildren().add(subMenuPane);
    }

    /***
     * Hides menu
     */
    public static void hideMenu() {
        subMenuPane.setVisible(false);
        bottomButts.setVisible(false);
    }

    /***
     * Displays menu and resizes it
     */
    public static void showMenu() {
        subMenuPane.setVisible(true);
        bottomButts.setVisible(true);

        int width = Helper.screenWidth(), height = Helper.screenHeight();
        double windowWidth = 340;
        double windotHeight = 360;
        subMenuPane.setPrefSize(windowWidth, windotHeight);
        subMenuPane.setMaxSize(windowWidth, windotHeight);
        subMenuPane.setMinSize(windowWidth, windotHeight);
        subMenuPane.setLayoutX(width / 2.0 - windowWidth / 2.0);
        subMenuPane.setLayoutY(height / 2.0 - windotHeight / 2.0);
    }

    /***
     * Returns HashMap where every TextField is stored
     */
    public static HashMap<String, TextField> getTextFields() {
        return textFields;
    }

    /***
     * Returns HashMap where every CheckBox is stored
     */
    public static HashMap<String, CheckBox> getCheckBoxes() {
        return checkBoxes;
    }

    /***
     * Returns HashMap where every ComboBox is stored
     */
    public static HashMap<String, ComboBox> getComboBoxes() {
        return comboBoxes;
    }

    /***
     * Returns ListView where every available map is stored
     */
    public static ListView<String> getMapList() {
        return mapList;
    }

    public void playButtonSound() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        String fileName = "audio/wpn_moveselect.wav";
        InputStream bottomSound = AudioResources.class.getClassLoader().getResourceAsStream(fileName);
        AudioInputStream a = AudioSystem.getAudioInputStream(bottomSound);
        AudioFormat format = a.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, format);
        Clip clipS = (Clip) AudioSystem.getLine(info);
        clipS.open(a);   // Open music file
        // Set volumen value
        FloatControl gainControl = (FloatControl) clipS.getControl(FloatControl.Type.MASTER_GAIN);
        double gain = Double.valueOf(Configuration.getInstance().getVolumeInterface()) * Double.valueOf(Configuration.getInstance().getVolumeMaster()) / 10000; // gain entre 0 y 1
        float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0);
        gainControl.setValue(dB);
        clipS.start();      // Play clip
    }

    public void playExitButtonSound() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        String fileName = "audio/wpn_select.wav";
        InputStream bottomSound = AudioResources.class.getClassLoader().getResourceAsStream(fileName);
        AudioInputStream a = AudioSystem.getAudioInputStream(bottomSound);
        AudioFormat format = a.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, format);
        Clip clipS = (Clip) AudioSystem.getLine(info);
        clipS.open(a);   // Open music file
        // Set volumen value
        FloatControl gainControl = (FloatControl) clipS.getControl(FloatControl.Type.MASTER_GAIN);
        double gain = Double.valueOf(Configuration.getInstance().getVolumeInterface()) * Double.valueOf(Configuration.getInstance().getVolumeMaster()) / 10000; // gain entre 0 y 1
        float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0);
        gainControl.setValue(dB);
        clipS.start();      // Play clip
    }
}
